🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / jvmTest / kotlin / org / meshtastic / feature / firmware / ZipExtractionTest.kt
Displaying Raw • Download
feature/firmware/src/jvmTest/kotlin/org/meshtastic/feature/firmware/ZipExtractionTest.kt 01cd54907d62cd90913d3d071b6bc240cae365ef (01cd5490) Text, 9.20 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.firmware
Tff7b72import T7ee787java.io.ByteArrayInputStream
Tff7b72import T7ee787java.io.ByteArrayOutputStream
Tff7b72import T7ee787java.io.FilterInputStream
Tff7b72import T7ee787java.io.InputStream
Tff7b72import T7ee787java.util.zip.ZipEntry
Tff7b72import T7ee787java.util.zip.ZipOutputStream
Tff7b72import T7ee787kotlin.random.Random
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertFailsWith
Tff7b72import T7ee787kotlin.test.assertNull
Tff7b72import T7ee787kotlin.test.assertTrue
Tff7b72class T56d364ZipExtractionTest Tb4b4b4{
T8b949e/** Counts bytes actually pulled from the underlying source, so a test can assert reads stop early. */
Tff7b72private Tff7b72class T56d364CountingStreamTb4b4b4(Te6edf3delegateTb4b4b4: Te6edf3InputStreamTb4b4b4) Tb4b4b4: Te6edf3FilterInputStreamTb4b4b4(Te6edf3delegateTb4b4b4) Tb4b4b4{
Tff7b72var Te6edf3bytesRead Tff7b72= T79c0ff0L
Te6edf3private Tff7b72set
Tff7b72override Tff7b72fun Td2a8ffreadTb4b4b4(Te6edf3bTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3offTb4b4b4: Tffa657IntTb4b4b4, Te6edf3lenTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Int Tff7b72=
Tff7b72superTb4b4b4.Te6edf3readTb4b4b4(Te6edf3bTb4b4b4, Te6edf3offTb4b4b4, Te6edf3lenTb4b4b4)Tb4b4b4.Te6edf3also Tb4b4b4{ Tff7b72if Tb4b4b4(Tffa657it Tff7b72> T79c0ff0Tb4b4b4) Te6edf3bytesRead Tff7b72+Tff7b72= Tffa657it Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffreadTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Int Tff7b72= Tff7b72superTb4b4b4.Te6edf3readTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3also Tb4b4b4{ Tff7b72if Tb4b4b4(Tffa657it Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Te6edf3bytesReadTff7b72+Tff7b72+ Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffzipOfTb4b4b4(Tff7b72vararg Te6edf3entriesTb4b4b4: Te6edf3PairTff7b72<Tffa657StringTb4b4b4, Te6edf3ByteArrayTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3ByteArray Tb4b4b4{
Tff7b72val Te6edf3out Tff7b72= Te6edf3ByteArrayOutputStreamTb4b4b4(Tb4b4b4)
Te6edf3ZipOutputStreamTb4b4b4(Tff7b72outTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3zip Tff7b72-Tff7b72>
Te6edf3entriesTb4b4b4.Te6edf3forEach Tb4b4b4{ Tb4b4b4(Te6edf3nameTb4b4b4, Te6edf3bytesTb4b4b4) Tff7b72-Tff7b72>
Te6edf3zipTb4b4b4.Te6edf3putNextEntryTb4b4b4(Te6edf3ZipEntryTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4)
Te6edf3zipTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3bytesTb4b4b4)
Te6edf3zipTb4b4b4.Te6edf3closeEntryTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72return Tff7b72outTb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)
Tb4b4b4}
T8b949e// ---------- readAtMost: the ordering property ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`readAtMost stops reading once the limit is passed`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// This is the property the previous implementation got wrong: it inflated an entry fully and only then compared
T8b949e// the size, so the allocation that exhausts the heap had already happened. Asserting on bytes actually pulled
T8b949e// from the source is what distinguishes "checked before" from "checked after".
Tff7b72val Te6edf3tenMegabytes Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff1T79c0ff0 Tff7b72* T79c0ff1T79c0ff0T79c0ff2T79c0ff4 Tff7b72* T79c0ff1T79c0ff0T79c0ff2T79c0ff4Tb4b4b4)
Tff7b72val Te6edf3counting Tff7b72= Te6edf3CountingStreamTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3tenMegabytesTb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3readAtMostTb4b4b4(Te6edf3countingTb4b4b4, Te6edf3limit Tff7b72= T79c0ff1T79c0ff0T79c0ff2T79c0ff4Tb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffa source over the limit must be rejectedTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3countingTb4b4b4.Te6edf3bytesRead Tff7b72<Tff7b72= T79c0ff1T79c0ff0T79c0ff2T79c0ff4 Tff7b72+ T79c0ff8T79c0ff1T79c0ff9T79c0ff2Tb4b4b4,
Ta5d6ff"Ta5d6ffmust stop near the limit, but read Tffd700${Te6edf3countingTb4b4b4.Te6edf3bytesReadTffd700}Ta5d6ff of Tffd700${Te6edf3tenMegabytesTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytesTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`readAtMost returns the content when it fits`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3payload Tff7b72= Ta5d6ff"Ta5d6fffirmwareTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3readAtMostTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3payloadTb4b4b4)Tb4b4b4, Te6edf3limit Tff7b72= T79c0ff1T79c0ff0T79c0ff2T79c0ff4Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmwareTa5d6ff"Tb4b4b4, Te6edf3resultTff7b72?.Te6edf3decodeToStringTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`readAtMost accepts a source exactly at the limit`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3payload Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff6T79c0ff4Tb4b4b4) Tb4b4b4{ T79c0ff1 Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff6T79c0ff4Tb4b4b4, Te6edf3readAtMostTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3payloadTb4b4b4)Tb4b4b4, Te6edf3limit Tff7b72= T79c0ff6T79c0ff4Tb4b4b4)Tff7b72?.Te6edf3sizeTb4b4b4)
Te6edf3assertNullTb4b4b4(Te6edf3readAtMostTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3payloadTb4b4b4)Tb4b4b4, Te6edf3limit Tff7b72= T79c0ff6T79c0ff3Tb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e// ---------- extractZipEntriesBounded ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`a normal archive extracts every entry`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3zip Tff7b72= Te6edf3zipOfTb4b4b4(Ta5d6ff"Ta5d6fffirmware.binTa5d6ff" Te6edf3to Te6edf3ByteArrayTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4) Tb4b4b4{ T79c0ff7 Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffmanifest.jsonTa5d6ff" Te6edf3to Ta5d6ff"Ta5d6ff{}Ta5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3entries Tff7b72= Te6edf3extractZipEntriesBoundedTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3zipTb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3setOfTb4b4b4(Ta5d6ff"Ta5d6fffirmware.binTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffmanifest.jsonTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3entriesTb4b4b4.Te6edf3keysTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3T79c0ff2Tb4b4b4, Te6edf3entriesTff7b72[Ta5d6ff"Ta5d6fffirmware.binTa5d6ff"Tff7b72]Tff7b72?.Te6edf3sizeTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`a compression bomb is refused without inflating it`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// 8 MiB of zeros compresses to a few KB. With a small budget this must abort during the entry, not after.
Tff7b72val Te6edf3bomb Tff7b72= Te6edf3zipOfTb4b4b4(Ta5d6ff"Ta5d6ffbomb.binTa5d6ff" Te6edf3to Te6edf3ByteArrayTb4b4b4(T79c0ff8 Tff7b72* T79c0ff1T79c0ff0T79c0ff2T79c0ff4 Tff7b72* T79c0ff1T79c0ff0T79c0ff2T79c0ff4Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3counting Tff7b72= Te6edf3CountingStreamTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3bombTb4b4b4)Tb4b4b4)
Te6edf3assertFailsWithTff7b72<Te6edf3IllegalArgumentExceptionTff7b72> Tb4b4b4{ Te6edf3extractZipEntriesBoundedTb4b4b4(Te6edf3countingTb4b4b4, Te6edf3maxTotalBytes Tff7b72= T79c0ff4T79c0ff0T79c0ff9T79c0ff6Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`the uncompressed budget is enforced across entries, not per entry`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Three entries each under the budget but over it in total — a per-entry check would let this through.
Tff7b72val Te6edf3zip Tff7b72= Te6edf3zipOfTb4b4b4(Ta5d6ff"Ta5d6ffaTa5d6ff" Te6edf3to Te6edf3ByteArrayTb4b4b4(T79c0ff2T79c0ff0T79c0ff4T79c0ff8Tb4b4b4) Tb4b4b4{ T79c0ff1 Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffbTa5d6ff" Te6edf3to Te6edf3ByteArrayTb4b4b4(T79c0ff2T79c0ff0T79c0ff4T79c0ff8Tb4b4b4) Tb4b4b4{ T79c0ff2 Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffcTa5d6ff" Te6edf3to Te6edf3ByteArrayTb4b4b4(T79c0ff2T79c0ff0T79c0ff4T79c0ff8Tb4b4b4) Tb4b4b4{ T79c0ff3 Tb4b4b4}Tb4b4b4)
Te6edf3assertFailsWithTff7b72<Te6edf3IllegalArgumentExceptionTff7b72> Tb4b4b4{
Te6edf3extractZipEntriesBoundedTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3zipTb4b4b4)Tb4b4b4, Te6edf3maxTotalBytes Tff7b72= T79c0ff5T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`too many entries is refused`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3many Tff7b72= Te6edf3ArrayTb4b4b4(T79c0ff2T79c0ff0Tb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffentryTffd700$Te6edf3itTa5d6ff" Te6edf3to Te6edf3ByteArrayTb4b4b4(T79c0ff4Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3zip Tff7b72= Te6edf3zipOfTb4b4b4(Tff7b72*Te6edf3manyTb4b4b4)
Te6edf3assertFailsWithTff7b72<Te6edf3IllegalArgumentExceptionTff7b72> Tb4b4b4{
Te6edf3extractZipEntriesBoundedTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3zipTb4b4b4)Tb4b4b4, Te6edf3maxEntries Tff7b72= T79c0ff1T79c0ff0Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Builds an archive containing [count] zero-byte STORED entries that all share [name].
*
* `ZipOutputStream` refuses to write a duplicate name, so this emits the local file headers directly — which is how
* a hostile archive would be produced anyway. `ZipInputStream` reads local headers sequentially and stops at the
* end-of-central-directory signature, so it yields all [count] entries.
*/
Tff7b72private Tff7b72fun Td2a8ffzipWithDuplicateNamesTb4b4b4(Te6edf3nameTb4b4b4: Tffa657StringTb4b4b4, Te6edf3countTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tb4b4b4{
Tff7b72val Te6edf3out Tff7b72= Te6edf3ByteArrayOutputStreamTb4b4b4(Tb4b4b4)
Tff7b72fun Td2a8ffle16Tb4b4b4(Te6edf3vTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72outTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3v Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4)
Tff7b72outTb4b4b4.Te6edf3writeTb4b4b4(Tb4b4b4(Te6edf3v Te6edf3ushr T79c0ff8Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4)
Tb4b4b4}
Tff7b72fun Td2a8ffle32Tb4b4b4(Te6edf3vTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Te6edf3le16Tb4b4b4(Te6edf3v Te6edf3and T79c0ff0Te6edf3xFFFFTb4b4b4)
Te6edf3le16Tb4b4b4(Tb4b4b4(Te6edf3v Te6edf3ushr T79c0ff1T79c0ff6Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFFFTb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3nameBytes Tff7b72= Te6edf3nameTb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)
Te6edf3repeatTb4b4b4(Te6edf3countTb4b4b4) Tb4b4b4{
Te6edf3le32Tb4b4b4(T79c0ff0Te6edf3x04034B50Tb4b4b4) T8b949e// local file header signature
Te6edf3le16Tb4b4b4(T79c0ff2T79c0ff0Tb4b4b4) T8b949e// version needed
Te6edf3le16Tb4b4b4(T79c0ff0Tb4b4b4) T8b949e// flags
Te6edf3le16Tb4b4b4(T79c0ff0Tb4b4b4) T8b949e// method: stored
Te6edf3le16Tb4b4b4(T79c0ff0Tb4b4b4) T8b949e// mod time
Te6edf3le16Tb4b4b4(T79c0ff0Tb4b4b4) T8b949e// mod date
Te6edf3le32Tb4b4b4(T79c0ff0Tb4b4b4) T8b949e// crc32 of empty data
Te6edf3le32Tb4b4b4(T79c0ff0Tb4b4b4) T8b949e// compressed size
Te6edf3le32Tb4b4b4(T79c0ff0Tb4b4b4) T8b949e// uncompressed size
Te6edf3le16Tb4b4b4(Te6edf3nameBytesTb4b4b4.Te6edf3sizeTb4b4b4)
Te6edf3le16Tb4b4b4(T79c0ff0Tb4b4b4) T8b949e// extra length
Tff7b72outTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3nameBytesTb4b4b4)
Tb4b4b4}
Te6edf3le32Tb4b4b4(T79c0ff0Te6edf3x06054B50Tb4b4b4) T8b949e// end of central directory: stops the stream reader
Te6edf3repeatTb4b4b4(T79c0ff1T79c0ff8Tb4b4b4) Tb4b4b4{ Tff7b72outTb4b4b4.Te6edf3writeTb4b4b4(T79c0ff0Tb4b4b4) Tb4b4b4}
Tff7b72return Tff7b72outTb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`the compressed side is bounded even when the declared size is unknown`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// getFileSize reports 0 for a provider that declines to answer, so the declared-size gate passes vacuously.
T8b949e// Seeded-random content so deflate cannot shrink it — a regular pattern here compresses to a few hundred bytes
T8b949e// and the bound would never be reached, making the test pass for the wrong reason.
Tff7b72val Te6edf3incompressible Tff7b72= Te6edf3RandomTb4b4b4(Te6edf3seed Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4)Tb4b4b4.Te6edf3nextBytesTb4b4b4(T79c0ff2T79c0ff5T79c0ff6 Tff7b72* T79c0ff1T79c0ff0T79c0ff2T79c0ff4Tb4b4b4)
Tff7b72val Te6edf3zip Tff7b72= Te6edf3zipOfTb4b4b4(Ta5d6ff"Ta5d6fffirmware.binTa5d6ff" Te6edf3to Te6edf3incompressibleTb4b4b4)
Te6edf3assertFailsWithTff7b72<Te6edf3IllegalArgumentExceptionTff7b72> Tb4b4b4{
Te6edf3extractZipEntriesBoundedTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3zipTb4b4b4)Tb4b4b4, Te6edf3maxCompressedBytes Tff7b72= T79c0ff4T79c0ff0T79c0ff9T79c0ff6Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`a normal archive is unaffected by the compressed bound`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3zip Tff7b72= Te6edf3zipOfTb4b4b4(Ta5d6ff"Ta5d6fffirmware.binTa5d6ff" Te6edf3to Te6edf3ByteArrayTb4b4b4(T79c0ff6T79c0ff4Tb4b4b4) Tb4b4b4{ T79c0ff3 Tb4b4b4}Tb4b4b4)
Tff7b72val Te6edf3entries Tff7b72= Te6edf3extractZipEntriesBoundedTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3zipTb4b4b4)Tb4b4b4, Te6edf3maxCompressedBytes Tff7b72= T79c0ff1T79c0ff0T79c0ff2T79c0ff4 Tff7b72* T79c0ff1T79c0ff0T79c0ff2T79c0ff4Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3setOfTb4b4b4(Ta5d6ff"Ta5d6fffirmware.binTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3entriesTb4b4b4.Te6edf3keysTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`duplicate entry names cannot bypass the entry cap`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// The cap counted map keys, and duplicates collapse to one key — so an archive of arbitrarily many same-named
T8b949e// entries walked straight past it while doing unbounded work.
Tff7b72val Te6edf3zip Tff7b72= Te6edf3zipWithDuplicateNamesTb4b4b4(Ta5d6ff"Ta5d6ffsame.binTa5d6ff"Tb4b4b4, Te6edf3count Tff7b72= T79c0ff5T79c0ff0Tb4b4b4)
Te6edf3assertFailsWithTff7b72<Te6edf3IllegalArgumentExceptionTff7b72> Tb4b4b4{
Te6edf3extractZipEntriesBoundedTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3zipTb4b4b4)Tb4b4b4, Te6edf3maxEntries Tff7b72= T79c0ff1T79c0ff0Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`the duplicate-name fixture really does yield repeated entries`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Guards the fixture itself: if ZipInputStream collapsed or rejected these, the cap test above would pass for
T8b949e// the wrong reason.
Tff7b72val Te6edf3zip Tff7b72= Te6edf3zipWithDuplicateNamesTb4b4b4(Ta5d6ff"Ta5d6ffsame.binTa5d6ff"Tb4b4b4, Te6edf3count Tff7b72= T79c0ff5Tb4b4b4)
Tff7b72val Te6edf3entries Tff7b72= Te6edf3extractZipEntriesBoundedTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Te6edf3zipTb4b4b4)Tb4b4b4, Te6edf3maxEntries Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3setOfTb4b4b4(Ta5d6ff"Ta5d6ffsame.binTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3entriesTb4b4b4.Te6edf3keysTb4b4b4, Ta5d6ff"Ta5d6ffduplicates collapse to one key — that is the bug's premiseTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`directory entries do not consume the entry budget`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3out Tff7b72= Te6edf3ByteArrayOutputStreamTb4b4b4(Tb4b4b4)
Te6edf3ZipOutputStreamTb4b4b4(Tff7b72outTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3zip Tff7b72-Tff7b72>
Te6edf3repeatTb4b4b4(T79c0ff2T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3zipTb4b4b4.Te6edf3putNextEntryTb4b4b4(Te6edf3ZipEntryTb4b4b4(Ta5d6ff"Ta5d6ffdirTffd700$Te6edf3itTa5d6ff/Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3zipTb4b4b4.Te6edf3closeEntryTb4b4b4(Tb4b4b4)
Tb4b4b4}
Te6edf3zipTb4b4b4.Te6edf3putNextEntryTb4b4b4(Te6edf3ZipEntryTb4b4b4(Ta5d6ff"Ta5d6fffirmware.binTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3zipTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3ByteArrayTb4b4b4(T79c0ff8Tb4b4b4)Tb4b4b4)
Te6edf3zipTb4b4b4.Te6edf3closeEntryTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3entries Tff7b72= Te6edf3extractZipEntriesBoundedTb4b4b4(Te6edf3ByteArrayInputStreamTb4b4b4(Tff7b72outTb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3maxEntries Tff7b72= T79c0ff2Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3setOfTb4b4b4(Ta5d6ff"Ta5d6fffirmware.binTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3entriesTb4b4b4.Te6edf3keysTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.09s